package com.to8to.autotask.allocate.newproject;

import static com.to8to.autotask.util.AutoTaskType.ALLOCATE_SHKF;
import static com.to8to.autotask.util.Constant.CHANNEL_PC;
import static com.to8to.autotask.util.Constant.RECEIVE_STATUS_ALL;
import static com.to8to.autotask.util.Constant.RECEIVE_STATUS_APP;
import static com.to8to.autotask.util.Constant.RECEIVE_STATUS_PC;
import static com.to8to.autotask.util.Constant.ignoreRepeatedPhones;
import static com.to8to.autotask.util.DBOperaType.UPDATE;
import static com.to8to.es.db.dao.DBTables.TABLE_APPLY;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.elasticsearch.common.collect.Lists;
import org.elasticsearch.common.collect.Maps;
import org.elasticsearch.common.collect.Sets;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.to8to.autotask.heartbeat.CustServicerOnline;
import com.to8to.autotask.rpc.SendMsg2ES;
import com.to8to.autotask.util.Constant;
import com.to8to.autotask.util.DBOperaType;
import com.to8to.autotask.util.DBUtil;
import com.to8to.autotask.util.TwoTuple;
import com.to8to.autotask.util.Utils;
import com.to8to.commons.utils.StringUtil;
import com.to8to.es.db.dao.DBTables;
import com.to8to.es.util.LogUtil;
import com.to8to.thrift.crm.itemdb.TItem;

/**
 * AllocateProjectAlgorithm.java
 * 作 者:atom.liu
 * 版 本:v2.0
 * 日期:2015年02月06日
 * 描 述:分配新项目给审核客服审核的算法类。
 */
final class AllocateProjectAlgorithm
{
    private static final Logger logger = LoggerFactory.getLogger(AllocateProjectAlgorithm.class);

    /**
     * 客服排序策略，根据已分配有效项目数升序排序
     */
    private static final StrategyWithAllocated STRATEGY_ALLOCATED = new StrategyWithAllocated();

    /**
     * SQL：获取在职的审核客服，且当前负责某些渠道的
     */
    private static final String ALL_CUSTSERVICER_SQL =
        "select * from to8to_user_info where isclose = 0 and crmroleid in(1,15) and receive_status != 1 and cityids != '' and cityids is not null";

    /**
     * SQL:分配审核客服(设置项目的check_uid 和 distrubution_time)
     */
    private static final String ALLOCATE_SQL = "update to8to_yuyue_apply set check_uid=%s ,distrubution_time=%s where yid in (%s)";

    /**
     * 查询最近新登记项目的SQL（还未分配审核客服）， 只需要查询出手机号即可
     */
    private static final String RECENT_NEWPROJECT_SQL =
        "select distinct e.eid,e.str,a.yid from to8to_yuyue_apply as a left join to8to_encode as e on a.phoneid = e.eid where a.uptime >= ? and a.ispass = 0 and (a.check_uid = 0 or a.check_uid is null) and e.type = 1";

    /**
     * 查询最近半年项目的SQL， 根据手机号进行过滤。
     */
    private static final String HALFYEAR_PROJECT_SQL =
        "select * from to8to_yuyue_apply as a left join to8to_encode as e on a.phoneId = e.eid where a.uptime >= ? and e.eid in (%s) and e.type = 1";

    /**
     * 查询当前可分配的客服
     */
    private static final String ASSIGNABLE_CUSTSERVICER_SQL =
        "select * from to8to_user_info where isclose = 0 and receive_status != 1 and cityids is not null and cityids != '' and uid in (%s) and crmroleid in (1,15)";

    /**
     * 自动分配最近的项目
     * @param isAutoTrigger true：自动触发； false：手动触发
     */
    public static void autoAllocate(boolean isAutoTrigger)
    {
        Connection connect = null;

        List<String> allocateSqlList = Lists.newArrayList();//所有要更新的sql语句集合
        List<String> allUpdateYids = Lists.newArrayList(); //所有更新的yid集合，通知ES刷新时需要用到

        try
        {
            connect = DBUtil.getConnection(false);

            TwoTuple<Set<Long>, Set<Long>> tuple = listNewProjectPhone(connect, isAutoTrigger);
            if (Utils.isNullOrEmpty(tuple.first) && Utils.isNullOrEmpty(tuple.second))
            {
                logger.info("本次执行没有新登记的项目需要分配！");
                return;
            }

            Map<String, List<TItem>> phoneProjectMap = listHalfYearProject(connect, tuple.first, tuple.second);

            List<CustServicer> servicers = getAssignableCustServicer(connect, -1);
            if (Utils.isNullOrEmpty(servicers))
            {
                logger.info("没有在线的客服！");
                return;
            }

            //查询出全部的未离职的审核客服，分配重复项目时需要使用
            Map<Integer, List<Integer>> notCloseMap = getAllCustServicer(connect);//所有未离职的审核客服集合(包括在线和不在线的)
            if (notCloseMap.size() == 0)
            {
                logger.error("没有在职的客服！");
                return;
            }

            Map<Integer, CustServicer> uidCustServierMap = groupCustServicerById(servicers);
            TwoTuple<Map<Integer, Map<Integer, List<CustServicer>>>, Map<Integer, List<CustServicer>>> tupleResult =
                groupCustServicerByChannelAndCity(servicers);
            Map<Integer, Map<Integer, List<CustServicer>>> channelCityMap = tupleResult.first;
            Map<Integer, List<CustServicer>> specCityMap = tupleResult.second;

            for (String phone : phoneProjectMap.keySet())
            {
                List<TItem> projects = phoneProjectMap.get(phone);
                //==================新登记的项目，没有重复项目===================
                if (projects.size() == 1)
                {
                    TItem project = projects.get(0);
                    int cityid = project.getCityid();
                    String yid = String.valueOf(project.getYid());
                    int receiveStatus = getReceiveStatusByPTag(project.getPtag());
                    List<CustServicer> list = getCustServicersByChannelAndCity(channelCityMap, specCityMap, receiveStatus, cityid);
                    executeAllocateWithCityid(cityid, list, allocateSqlList, yid, allUpdateYids);
                }
                //===============有重复的项目=============================
                else
                {
                    List<TItem> needAllocateProjects = Lists.newArrayList();
                    List<String> needAllocateYids = Lists.newArrayList();
                    //已经分配过的重复项目
                    TItem repeat = null;
                    //遍历获取需要进行分配的项目，去除掉有审核负责人的重复项目
                    for (TItem project : projects)
                    {
                        int checkUid = project.getCheck_uid();
                        if (project.getIspass() != 0)//之前通过审核的重复项目
                        {
                            int distrubutionTime = project.getDistrubution_time();
                            //有多个重复项目，且都有审核负责人，取最近的审核负责人
                            if (checkUid != 0
                                && notCloseMap.containsKey(checkUid)
                                && (repeat == null || repeat.getDistrubution_time() < distrubutionTime))
                                repeat = project;
                        }
                        else
                        {
                            if (checkUid != 0 && uidCustServierMap.keySet().contains(checkUid))
                            {
                                repeat = project;
                            }
                            else
                            { //待审核的项目，即使项目已经分配给了一个客服，但是ispass=0，并且这个客服没有在线， 则也进行重新分配
                                needAllocateYids.add(String.valueOf(project.getYid()));
                                needAllocateProjects.add(project);
                            }
                        }
                    }

                    String yids = Utils.collect2Str(needAllocateYids);
                    //将项目分配给之前重复项目的审核负责人
                    if (repeat != null)
                    {
                        int repeatCheckUid = repeat.getCheck_uid();
                        allocateSqlList.add(String.format(ALLOCATE_SQL, repeatCheckUid, Utils.getCurrentSecond(), yids));
                        allUpdateYids.addAll(needAllocateYids);

                        if (uidCustServierMap.containsKey(repeatCheckUid))
                        {
                            CustServicer repeatCS = uidCustServierMap.get(repeatCheckUid);
                            repeatCS.setCurrentAllocated(repeatCS.getCurrentAllocated() + 1);
                            logger.info(String.format("项目[ %s ] 分配到  审核客服  [ %s  %s]", yids, repeatCheckUid, repeatCS.getUsername()));
                        }
                        else
                        {
                            logger.info(String.format("项目[ %s ] 分配到  审核客服  [ %s ]", yids, repeatCheckUid));
                        }
                    }
                    //重新选取审核客服进行分配
                    else
                    {
                        executeRepeatProjects(needAllocateProjects, channelCityMap, specCityMap, allocateSqlList, yids, allUpdateYids);
                    }
                }
            }

            //分配完成，刷新到数据库
            DBUtil.refresh2DB(connect, allocateSqlList, ALLOCATE_SHKF, new SendMsg2ES(UPDATE, TABLE_APPLY, allUpdateYids));
        }
        catch (Throwable e)
        {
            logger.error("自动分配新登记项目出现异常！{} \n {}", e, LogUtil.logStackTrace(e));
            DBUtil.rollback(connect);
        }
        finally
        {
            DBUtil.releaseSource(null, null, connect);
        }
    }

    /**
     * 分配单独一个项目。
     * @param yid 新登记的项目id，
     * @return -1:服务器端异常；-2：传入参数错误；0：没有可分配客服，暂停分配；1：分配成功
     */
    public static int allocateSingle(int yid, long phoneId)
    {
        logger.info(String.format("分配单个项目开始！项目id：[ %s ]，PhoneId：[ %s ]", yid, phoneId));
        Connection connect = null;
        try
        {
            connect = DBUtil.getConnection(false);

            Map<Integer, List<Integer>> notCloseMap = getAllCustServicer(connect);//所有未离职的审核客服集合(包括在线和不在线的)
            if (notCloseMap.size() == 0)
            {
                logger.error("没有在职的客服！");
                return 0;
            }

            String phone = getPhoneByPhoneId(phoneId);
            if (StringUtil.isEmpty(phone))
            {
                logger.error(String.format("传入的phoneId [ %s ] 查不到对应的手机号", phoneId));
                return -2;
            }
            Set<Long> normalPhoneIds = Sets.newHashSet();
            Set<Long> ignorePhoneIds = Sets.newHashSet();
            if (ignoreRepeatedPhones.contains(phone))
                ignorePhoneIds.add(phoneId);
            else
                normalPhoneIds.add(phoneId);

            Map<String, List<TItem>> map = listHalfYearProject(connect, normalPhoneIds, ignorePhoneIds);
            List<TItem> items = map.get(phone);
            if (map.size() == 0 || Utils.isNullOrEmpty(items))
            {
                logger.error(String.format("传入的phoneId [ %s ]查不到对应的项目", phoneId));
                return -2;
            }

            //不存在重复项目
            if (items.size() == 1)
            {
                TItem item = items.get(0);
                //新登记的项目不应该有check_uid和distrubution_time
                if (yid != item.getYid() || item.getCheck_uid() != 0 || item.getDistrubution_time() != 0)
                {
                    logger.info("传入的yid和手机号不匹配");
                    return -2;
                }

                return executeAllocateSingle(connect, item.getCityid(), String.valueOf(yid));
            }
            else
            {
                List<TItem> needAllocateProjects = new ArrayList<TItem>();
                List<Integer> itemIds = new ArrayList<Integer>();
                TItem repeat = null; //已经分配过的重复项目
                //遍历获取需要进行分配的项目，去除掉有审核负责人的重复项目
                for (TItem project : items)
                {
                    int checkUid = project.getCheck_uid();
                    //之前通过审核的重复项目
                    if (project.getIspass() != 0)
                    {
                        int distrubutionTime = project.getDistrubution_time();
                        //如果有多个重复项目，且都有审核负责人，取最近的审核负责人
                        if ((checkUid != 0 && notCloseMap.containsKey(checkUid))
                            && (repeat == null || repeat.getDistrubution_time() < distrubutionTime))
                            repeat = project;
                    }
                    else
                    {
                        itemIds.add(project.getYid());
                        needAllocateProjects.add(project);
                    }
                }

                if (!itemIds.contains(yid))
                {
                    logger.info("传入的yid和手机号不匹配");
                    return -2;
                }

                String yids = Utils.collect2Str(itemIds);
                //将项目分配给之前重复项目的审核负责人
                if (repeat != null)
                {
                    int repeatCheckUid = repeat.getCheck_uid();
                    logger.info(String.format("项目[ %s ] 分配到  审核客服  [ %s ]", yids, repeatCheckUid));
                    DBUtil.refresh2DB(connect, String.format(ALLOCATE_SQL, repeatCheckUid, Utils.getCurrentSecond(), yids), ALLOCATE_SHKF, new SendMsg2ES(UPDATE, TABLE_APPLY, yids));
                    return 1;
                }
                //重新选取审核客服进行分配
                else
                {
                    return executeSingleRepeatProject(connect, yids, needAllocateProjects);
                }
            }
        }
        catch (SQLException e)
        {
            logger.error("分配新登记项目发生异常!" + e.getMessage(), e);
            return -1;
        }
        finally
        {
            DBUtil.releaseSource(null, null, connect);
        }
    }

    /**
     * 单个分配
     * @return -1:服务器端异常；-2：传入参数错误；0：没有可分配客服，暂停分配；1：分配成功 
     */
    private static int executeAllocateSingle(Connection connect, int cityid, String yids) throws SQLException
    {
        //从ES中查询出当前在线的客服
        List<CustServicer> servicers = getAssignableCustServicer(connect, cityid);
        if (servicers.size() == 0)
        {
            logger.info(String.format("项目[ %s ]所在城市[ %s ]没有负责的在线审核客服！", yids, cityid));
            return 0;
        }

        TwoTuple<Map<Integer, Map<Integer, List<CustServicer>>>, Map<Integer, List<CustServicer>>> tupleResult =
            groupCustServicerByChannelAndCity(servicers);
        int receiveStatus = getReceiveStatusByPTag(Constant.UPPROJECT_POPULARIZE_POST);
        List<CustServicer> list = getCustServicersByChannelAndCity(tupleResult.first, tupleResult.second, receiveStatus, cityid);
        if (Utils.isNullOrEmpty(list))
        {
            logger.info(String.format("项目[ %s ] 所在城市[ %s ]没有符合条件的审核客服", yids, cityid));
            return 0;
        }

        CustServicer target = list.get(0);
        logger.info(String.format("项目[ %s ] 分配到  审核客服  [ %s  %s]", yids, target.getUid(), target.getUsername()));
        DBUtil.refresh2DB(connect, String.format(ALLOCATE_SQL, target.getUid(), Utils.getCurrentSecond(), yids), ALLOCATE_SHKF, new SendMsg2ES(DBOperaType.UPDATE, DBTables.TABLE_APPLY, yids));
        return 1;
    }

    /**
     * 处理分配单个项目时，遇到重复项目，遍历进行分配，分配给重复项目所在城市有审核负责人的。
     */
    private static int executeSingleRepeatProject(Connection connect, String yids, List<TItem> needAllocateProjects) throws SQLException
    {
        Set<Integer> citySet = Sets.newHashSet();
        for (TItem item : needAllocateProjects)
        {
            int cityid = item.getCityid();
            if (citySet.contains(cityid))
                continue;
            List<CustServicer> servicers = getAssignableCustServicer(connect, cityid);
            if (Utils.isNullOrEmpty(servicers))
            {
                citySet.add(cityid);
                continue;
            }
            TwoTuple<Map<Integer, Map<Integer, List<CustServicer>>>, Map<Integer, List<CustServicer>>> tupleResult =
                groupCustServicerByChannelAndCity(servicers);
            int receiveStatus = getReceiveStatusByPTag(Constant.UPPROJECT_POPULARIZE_POST);
            List<CustServicer> list = getCustServicersByChannelAndCity(tupleResult.first, tupleResult.second, receiveStatus, cityid);
            if (Utils.isNullOrEmpty(list))
            {
                citySet.add(cityid);
                continue;
            }
            CustServicer target = list.get(0);
            logger.info(String.format("项目[ %s ] 分配到  审核客服  [ %s  %s]", yids, target.getUid(), target.getUsername()));
            DBUtil.refresh2DB(connect, String.format(ALLOCATE_SQL, target.getUid(), Utils.getCurrentSecond(), yids), ALLOCATE_SHKF, new SendMsg2ES(DBOperaType.UPDATE, DBTables.TABLE_APPLY, yids));
            return 1;
        }
        logger.debug(String.format("项目[ %s ] 各自所在的城市均没有符合条件的审核客服", yids));
        return 0;
    }

    /**
     * 查询出最近一周新登记项目的手机号码
     * 自动分配的时候，分配24小时内的新登记手机号；手动触发分配时，分配一周内的项目
     */
    private static TwoTuple<Set<Long>, Set<Long>> listNewProjectPhone(Connection connect, boolean isAutoTrigger) throws SQLException
    {
        PreparedStatement stat = connect.prepareStatement(RECENT_NEWPROJECT_SQL);
        if (isAutoTrigger)
            stat.setInt(1, Utils.getHoursAgo(24)); //自动分配的只分配24小时内登记的项目
        else
            stat.setInt(1, Utils.getHoursAgo(7 * 24)); //手动分配分配一周内登记的项目
        ResultSet result = stat.executeQuery();
        Set<String> phones = Sets.newHashSet();
        Set<Long> normalPhoneIds = Sets.newHashSet();
        Set<Long> ignorePhoneIds = Sets.newHashSet();
        while (result.next())
        {
            String phone = result.getString("e.str");
            int yid = result.getInt("yid");
            long phoneId = result.getLong("e.eid");
            if (StringUtil.isEmpty(phone))
            {
                logger.info(String.format("项目[ %s ] 的phone为空，将忽略此项目，不进行分配", yid));
                continue;
            }
            if (ignoreRepeatedPhones.contains(phone))
                ignorePhoneIds.add(phoneId);
            else
                normalPhoneIds.add(phoneId);

            phones.add(phone);
        }
        DBUtil.releaseSource(result, stat, null);
        logger.debug(String.format("查询出最近有新登记且未分配项目的手机号码个数: %s \r\n%s ", phones.size(), phones));
        return TwoTuple.newInstance(normalPhoneIds, ignorePhoneIds);
    }

    /**
     * 查询半年以来这些手机号登记的项目，包含了重复项目。
     * @return Map<String, List<TItem>> key:phone value:项目集合
     */
    private static Map<String, List<TItem>> listHalfYearProject(Connection connect, Set<Long> normalPhoneId, Set<Long> ignorePhoneIds)
            throws SQLException
    {
        Map<String, List<TItem>> map = Maps.newHashMap();

        List<TItem> ignoreNewestItems = getIgnorePhoneNewestItem(connect, ignorePhoneIds);

        if (!Utils.isNullOrEmpty(normalPhoneId))
        {
            PreparedStatement stat = connect.prepareStatement(String.format(HALFYEAR_PROJECT_SQL, Utils.collect2Str(normalPhoneId)));
            stat.setInt(1, Utils.getDayTimePoint(-180)); //时间点为半年
            ResultSet result = stat.executeQuery();

            while (result.next())
            {
                String phone = result.getString("e.str");
                if (StringUtil.isEmpty(phone))
                    continue;
                List<TItem> list = map.get(phone);
                if (list == null)
                {
                    list = Lists.newArrayList();
                    map.put(phone, list);
                }

                TItem project = new TItem();
                project.setCheck_uid(result.getInt("check_uid"));
                project.setYid(result.getInt("yid"));
                project.setPhone(phone);
                project.setPhoneId(result.getInt("phoneId"));
                project.setCityid(result.getInt("cityid"));
                project.setUptime(result.getInt("uptime"));
                project.setIspass(result.getInt("ispass"));
                project.setPtag(result.getString("ptag"));
                project.setDistrubution_time(result.getInt("distrubution_time"));
                list.add(project);
            }
            DBUtil.releaseSource(result, stat, null);
        }

        for (TItem item : ignoreNewestItems)
        {
            List<TItem> tmp = map.get(item.getPhone());
            if (tmp == null)
            {
                tmp = Lists.newArrayList();
                map.put(item.getPhone(), tmp);
            }
            tmp.add(item);
        }
        return map;
    }

    /**
     * 获取不需要考虑重复项目手机号的新登记项目 
     */
    private static List<TItem> getIgnorePhoneNewestItem(Connection connect, Set<Long> ignorePhoneIds) throws SQLException
    {
        List<TItem> list = Lists.newArrayList();
        if (!Utils.isNullOrEmpty(ignorePhoneIds))
        {
            String sql =
                "select a.*,e.str from to8to_yuyue_apply as a left join to8to_encode as e on a.phoneid = e.eid"
                    + " where e.eid in (%s) and (a.check_uid = 0 or a.check_uid is null) and  a.uptime >= ? and a.ispass = 0 and e.type = 1";
            PreparedStatement stat = connect.prepareStatement(String.format(sql, Utils.collect2Str(ignorePhoneIds)));
            stat.setInt(1, Utils.getHoursAgo(24));
            ResultSet result = stat.executeQuery();

            while (result.next())
            {
                String phone = result.getString("e.str");
                if (StringUtil.isEmpty(phone))
                    continue;
                TItem project = new TItem();
                project.setCheck_uid(result.getInt("a.check_uid"));
                project.setYid(result.getInt("a.yid"));
                project.setPhone(phone);
                project.setPhoneId(result.getInt("a.phoneId"));
                project.setCityid(result.getInt("a.cityid"));
                project.setUptime(result.getInt("a.uptime"));
                project.setIspass(result.getInt("a.ispass"));
                project.setPtag(result.getString("a.ptag"));
                project.setDistrubution_time(result.getInt("distrubution_time"));
                list.add(project);
            }
            DBUtil.releaseSource(result, stat, null);
        }
        return list;
    }

    /**
     * 获取在线的、可以分配的客服信息。
     * 未跟进的项目数统计全部的，已分配的项目数只统计今天的。
     * @param cityid 只查找这个城市下的客服，若查询全部，则传-1
     */
    private static List<CustServicer> getAssignableCustServicer(Connection connect, int cityid) throws SQLException
    {
        List<CustServicer> custServicers = new ArrayList<CustServicer>();
        List<Integer> onlineUids = CustServicerOnline.getOnline();

        if (onlineUids.size() == 0)
            return custServicers;

        PreparedStatement stat = connect.prepareStatement(String.format(ASSIGNABLE_CUSTSERVICER_SQL, Utils.collect2Str(onlineUids)));
        ResultSet result = stat.executeQuery();

        List<Integer> uidList = new ArrayList<Integer>();

        while (result.next())
        {
            List<Integer> cityids = Utils.parseIntegerList(result.getString("cityids"), ",");
            if (cityid != -1 && !cityids.contains(cityid))
                continue;

            CustServicer servicer = new CustServicer();
            int uid = result.getInt("uid");
            servicer.setReceiveStatus(result.getInt("receive_status"));
            servicer.setUid(uid);
            servicer.setUsername(result.getString("username"));
            servicer.setCityids(cityids);
            custServicers.add(servicer);
            uidList.add(uid);
        }

        DBUtil.releaseSource(result, stat, null);

        if (custServicers.size() == 0)
            return custServicers;

        //查询出当前客服今天已分配的项目个数：待审核(0)、可售(1通过审核)、延期可售(2待定)、不可售待开发(3未通过审核)计入统计。查询已分配的个数时，只查询当天的。
        String sql =
            "select check_uid, count(distinct(phoneid)) as count from to8to_yuyue_apply where ispass in (0,1,2,3) and check_uid in (%s) and distrubution_time >= ? group by check_uid";

        stat = connect.prepareStatement(String.format(sql, Utils.collect2Str(uidList)));
        stat.setInt(1, Utils.getDayTimePoint(0));
        result = stat.executeQuery();
        Map<Integer, Integer> currentAllocatedMap = new HashMap<Integer, Integer>();
        while (result.next())
        {
            int uid = result.getInt("check_uid");
            int count = result.getInt("count");
            currentAllocatedMap.put(uid, count);
        }

        StringBuilder logSB = new StringBuilder();
        for (CustServicer servicer : custServicers)
        {
            Integer currentAllocated = currentAllocatedMap.get(servicer.getUid());
            servicer.setCurrentAllocated(currentAllocated == null ? 0 : currentAllocated);
            logSB.append(String.format("客服[ %s  %s] 当天已分配个数[ %s ]\r\n", servicer.getUid(), servicer.getUsername(), servicer.getCurrentAllocated()));
        }
        DBUtil.releaseSource(result, stat, null);
        logger.info(logSB.toString());
        return custServicers;
    }

    /**
     * 将客服按照uid分组
     */
    private static Map<Integer, CustServicer> groupCustServicerById(List<CustServicer> servicers)
    {
        Map<Integer, CustServicer> map = Maps.newHashMap();
        for (CustServicer cs : servicers)
            map.put(cs.getUid(), cs);
        return map;
    }

    /**
     * 将客服按照渠道、城市进行分组
     * @param servicers
     */
    private static TwoTuple<Map<Integer, Map<Integer, List<CustServicer>>>, Map<Integer, List<CustServicer>>> groupCustServicerByChannelAndCity(
            List<CustServicer> servicers)
    {
        Map<Integer, Map<Integer, List<CustServicer>>> channelCityMap = Maps.newHashMap();
        channelCityMap.put(RECEIVE_STATUS_ALL, Maps.newHashMap());
        channelCityMap.put(RECEIVE_STATUS_APP, Maps.newHashMap());
        channelCityMap.put(RECEIVE_STATUS_PC, Maps.newHashMap());
        Map<Integer, List<CustServicer>> specCityMap = Maps.newHashMap();
        for (CustServicer servicer : servicers)
        {
            int receiveStatus = servicer.getReceiveStatus();
            if (receiveStatus == 0) //如果receiveStatus是0，则每个渠道都需要进行添加。
            {
                Map<Integer, List<CustServicer>> cityMap_0 = channelCityMap.get(RECEIVE_STATUS_ALL);
                Map<Integer, List<CustServicer>> cityMap_3 = channelCityMap.get(RECEIVE_STATUS_APP);
                Map<Integer, List<CustServicer>> cityMap_4 = channelCityMap.get(RECEIVE_STATUS_PC);

                for (Integer cityid : servicer.getCityids())
                {
                    if (Constant.SPECIAL_CITY.contains(cityid))
                    {
                        List<CustServicer> list = specCityMap.get(cityid);
                        if (list == null)
                        {
                            list = Lists.newArrayList();
                            specCityMap.put(cityid, list);
                        }
                        list.add(servicer);
                    }

                    List<CustServicer> list_0 = cityMap_0.get(cityid);
                    if (list_0 == null)
                    {
                        list_0 = Lists.newArrayList();
                        cityMap_0.put(cityid, list_0);
                    }
                    list_0.add(servicer);

                    List<CustServicer> list_3 = cityMap_3.get(cityid);
                    if (list_3 == null)
                    {
                        list_3 = Lists.newArrayList();
                        cityMap_3.put(cityid, list_3);
                    }
                    list_3.add(servicer);

                    List<CustServicer> list_4 = cityMap_4.get(cityid);
                    if (list_4 == null)
                    {
                        list_4 = Lists.newArrayList();
                        cityMap_4.put(cityid, list_4);
                    }
                    list_4.add(servicer);
                }
            }
            else
            {
                Map<Integer, List<CustServicer>> cityMap = channelCityMap.get(receiveStatus);
                if (cityMap == null)
                {
                    cityMap = Maps.newHashMap();
                    channelCityMap.put(receiveStatus, cityMap);
                }

                for (Integer cityid : servicer.getCityids())
                {
                    if (Constant.SPECIAL_CITY.contains(cityid))
                    {
                        List<CustServicer> list = specCityMap.get(cityid);
                        if (list == null)
                        {
                            list = Lists.newArrayList();
                            specCityMap.put(cityid, list);
                        }
                        list.add(servicer);
                    }
                    List<CustServicer> list = cityMap.get(cityid);
                    if (list == null)
                    {
                        list = Lists.newArrayList();
                        cityMap.put(cityid, list);
                    }
                    list.add(servicer);
                }
            }
        }

        for (Map<Integer, List<CustServicer>> cityMap : channelCityMap.values())
            for (List<CustServicer> cityCustList : cityMap.values())
                Collections.sort(cityCustList, STRATEGY_ALLOCATED);

        for (List<CustServicer> cityCustList : specCityMap.values())
            Collections.sort(cityCustList, STRATEGY_ALLOCATED);

        return TwoTuple.newInstance(channelCityMap, specCityMap);
    }

    /**
     * 通过项目的渠道和城市获取所对应的审核客服集合 
     */
    private static List<CustServicer> getCustServicersByChannelAndCity(Map<Integer, Map<Integer, List<CustServicer>>> channelCityMap,
            Map<Integer, List<CustServicer>> cityMap, int receiveStatus, int cityid)
    {
        if (Constant.SPECIAL_CITY.contains(cityid))
            return cityMap.get(cityid);

        Map<Integer, List<CustServicer>> map = channelCityMap.get(receiveStatus);
        if (Utils.isNullOrEmpty(map))
            return null;
        return map.get(cityid);
    }

    /**
     * 通过city id执行分配
     * @param cityId 要分配的项目的城市id
     * @param cityCustServicer 要分配的项目所在城市的审核客服集合
     * @param timeFlag 分配发生的时间标识
     * @param allocateSqlList 要分配的sql语句集合
     * @param projectIds 要分配的项目id
     * @param allUpdateYids 全部要进行更新的yid集合
     */
    private static void executeAllocateWithCityid(int cityId, List<CustServicer> cityCustServicer, List<String> allocateSqlList, String projectIds,
            List<String> allUpdateYids)
    {
        if (cityCustServicer == null || cityCustServicer.size() == 0)//该项目所在城市没有审核客服，不做处理
        {
            logger.debug(String.format("项目[ %s ] 所在城市[ %s ]没有符合条件的审核客服", projectIds, cityId));
            return;
        }

        //对该城市的客服重新排序
        Collections.sort(cityCustServicer, STRATEGY_ALLOCATED);
        CustServicer target = cityCustServicer.get(0);

        logger.info(String.format("项目[ %s ] 分配到  审核客服  [ %s  %s]", projectIds, target.getUid(), target.getUsername()));
        allocateSqlList.add(String.format(ALLOCATE_SQL, target.getUid(), Utils.getCurrentSecond(), projectIds));
        String[] projectIdArr = projectIds.split(",");
        allUpdateYids.addAll(Arrays.asList(projectIdArr));
        target.setCurrentAllocated(target.getCurrentAllocated() + 1);
    }

    /**
     * 处理重复项目。 如果一个重复项目所在城市没有审核客服，就继续遍历，直到有审核客服的。
     */
    private static void executeRepeatProjects(List<TItem> needAllocateProjects, Map<Integer, Map<Integer, List<CustServicer>>> channelCityMap,
            Map<Integer, List<CustServicer>> specCityMap, List<String> allocateSqlList, String projectIds, List<String> allUpdateYids)
    {
        //先将城市按照登记时间倒序排序
        Collections.sort(needAllocateProjects, new Comparator<TItem>()
        {
            @Override
            public int compare(TItem o1, TItem o2)
            {
                return o2.getUptime() - o1.getUptime();
            }
        });

        Set<Integer> citySet = Sets.newHashSet();
        for (TItem item : needAllocateProjects)
        {
            int cityid = item.getCityid();
            if (citySet.contains(cityid))
                continue;
            int receiveStatus = getReceiveStatusByPTag(item.getPtag());
            List<CustServicer> list = getCustServicersByChannelAndCity(channelCityMap, specCityMap, receiveStatus, cityid);
            if (Utils.isNullOrEmpty(list))//该项目所在城市没有审核客服，则继续另外一个客服进行判断
            {
                citySet.add(cityid);
                continue;
            }
            executeAllocateWithCityid(cityid, list, allocateSqlList, projectIds, allUpdateYids);
            return;
        }
        logger.debug(String.format("项目[ %s ] 各自所在的城市均没有符合条件的审核客服", projectIds));
    }

    /**
     * 获取全部在职的审核客服 
     */
    private static Map<Integer, List<Integer>> getAllCustServicer(Connection connect) throws SQLException
    {
        Map<Integer, List<Integer>> map = Maps.newHashMap();

        PreparedStatement stat = connect.prepareStatement(ALL_CUSTSERVICER_SQL);
        ResultSet result = stat.executeQuery();
        while (result.next())
        {
            String cityids = result.getString("cityids");
            List<Integer> list = Utils.parseIntegerList(cityids, ",");
            map.put(result.getInt("uid"), list);
        }

        DBUtil.releaseSource(result, stat, null);
        return map;
    }

    /**
     * 根据phoneId获取phone
     */
    private static String getPhoneByPhoneId(long phoneId)
    {
        Object phone = DBUtil.getField("to8to_encode", "str", "type = 1 and eid", String.valueOf(phoneId));
        return phone == null ? null : phone.toString();
    }

    /**
     * 通过ptag获取需要的receiveStatus
     */
    private static int getReceiveStatusByPTag(String ptag)
    {
        if (StringUtil.isEmpty(ptag))//如果ptag为空，则receiveStatus设置为0（全部）
            return 0;

        for (String s : CHANNEL_PC)
            if (ptag.startsWith(s))
                return 4;

        return 3; //移动APP
    }

    /**
     * 静态内部类，排序策略： 根据分配的有效项目数进行排序，升序
     */
    private static class StrategyWithAllocated implements Comparator<CustServicer>
    {
        @Override
        public int compare(CustServicer o1, CustServicer o2)
        {
            return o1.getCurrentAllocated() - o2.getCurrentAllocated();
        }
    }
}
